package game;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Set;

import exceptions.AlreadyOccupiedException;
import exceptions.ImpossibleMoveException;
import exceptions.NotOccupiedException;
import exceptions.UnknownColorException;


public class GoBoard {
	
	
	final public static int BLACK = 1;
	final public static int WHITE = 2;
	final public static int EMPTY = 0;
	
	final private static String BLACK_STRING="X";
	final private static String WHITE_STRING="O";
	final private static String EMPTY_STRING=".";
	
	final public static int PASSING_MOVE = -1;
	
	final private static String[] ALPHABETIC_LABELS = {"A","B","C","D","E","F","G","H","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z"};
	private int size;
	
	// The content of the intersection (x,y) has index x*size+y
	private int[] board;
	private Set<Integer> free_intersections;
	
	public GoBoard (int board_size) {
		/*
		 * Create a new empty board of size board_size.
		 */
		this.size = board_size;
		this.board = new int[board_size*board_size];
		this.free_intersections = new HashSet<Integer>();
		for (int i = 0 ; i < size*size ; i++){
			board[i] = EMPTY;
			free_intersections.add(i);
		}
	}
	
	public GoBoard (GoBoard board) {
		this.size = board.getSize();
		this.board = board.getBoardArray().clone();
		this.free_intersections = new HashSet<Integer>();
		this.free_intersections.addAll(board.getFreeIntersections());
	}
	
	public int getSize() {
		return size;
	}
	
	public int[] getBoardArray() {
		return board;
	}
	
	public int getIntersectionColor(int position) throws ImpossibleMoveException {
		int y = position % size;
		int x = (position-y)/size;
		return getIntersectionColor(x,y);
	}
	
	public int getIntersectionColor(int x, int y) throws ImpossibleMoveException {
		if (x < 0 || x >= size || y < 0 || y >= size) {
			//TODO Raise good exception
			throw new ImpossibleMoveException(this, x*size+y);
		}
		return board[x*size+y];
	}
	
	public Set<Integer> getFreeIntersections() {
		return free_intersections;
	}
	
	public boolean isFree(int position) throws ImpossibleMoveException {
		int y = position % size;
		int x = (position-y)/size;
		return isFree(x,y);
	}
	
	public boolean isFree(int x, int y) throws ImpossibleMoveException {
		if (x < 0 || x >= size || y < 0 || y >= size) {
			//TODO Raise good exception
			throw new ImpossibleMoveException(this, x*size+y);
		}
		return (board[x*size+y] == EMPTY);
		
	}
	
	public void placeStone(int color, int position) throws AlreadyOccupiedException, UnknownColorException, ImpossibleMoveException {
		int y = position % size;
		int x = (position-y)/size;
		placeStone(color,x,y);
	}
	
	public void placeStone(int color, int x, int y) throws AlreadyOccupiedException, UnknownColorException, ImpossibleMoveException {
		if (!(isFree(x,y))) {
			throw new AlreadyOccupiedException(this,x*size+y);
		}
		if (color != BLACK && color != WHITE){
			throw new UnknownColorException(color);
		}
		board[x*size+y] = color;
		free_intersections.remove(x*size+y);
	}
	
	public int deleteStone(int position) throws ImpossibleMoveException, NotOccupiedException {
		int y = position % size;
		int x = (position-y)/size;
		return deleteStone(x,y);
	}
	
	public int deleteStone(int x, int y) throws ImpossibleMoveException, NotOccupiedException {
		if ((isFree(x,y))) {
			throw new NotOccupiedException(this,x*size+y);
		}
		int color = board[x*size+y];
		board[x*size+y] = EMPTY;
		free_intersections.add(x*size+y);
		return color;
	}
	
	public LinkedList<Integer> neighbours(int position) throws ImpossibleMoveException {
		int y = position % size;
		int x = (position-y)/size;
		return neighbours(x,y);
	}
	
	public LinkedList<Integer> neighbours(int x, int y) throws ImpossibleMoveException {
		if (x < 0 || x >= size || y < 0 || y >= size) {
			//TODO Raise good exception
			throw new ImpossibleMoveException(this,x*size+y);
		}
		LinkedList<Integer> res = new LinkedList<Integer>();
		int pos = x*size+y;
		if (x >= 1 && x < size-1) {
			res.add(pos-size);
			res.add(pos+size);
			if (y >= 1 && y < size-1) {
				res.add(pos-1);
				res.add(pos+1);
			} else if (y == 0) {
				res.add(pos+1);
			} else {
				res.add(pos-1);
			}
		} else if (x == 0) {
			res.add(pos+size);
			if (y >= 1 && y < size-1) {
				res.add(pos-1);
				res.add(pos+1);
			} else if (y == 0) {
				res.add(pos+1);
			} else {
				res.add(pos-1);
			}
		} else {
			res.add(pos-size);
			if (y >= 1 && y < size-1) {
				res.add(pos-1);
				res.add(pos+1);
			} else if (y == 0) {
				res.add(pos+1);
			} else {
				res.add(pos-1);
			}
		}
		return res;
	}
	
	public String toString() {
		String s = "    ";
		for (int i = 0 ; i < size ; i++) {
			s+=ALPHABETIC_LABELS[i]+" ";
		}
		s += "\n";
		for (int j = 0 ; j < size ; j++) {
			if (j<9) s += " ";
			s+=(Integer.toString(j+1)+"  ");
			for (int i = 0 ; i < size ; i++) {
				try {
					switch (getIntersectionColor(i,j)){
					case BLACK:
						s+=(BLACK_STRING+" ");
						break;
					case WHITE:
						s+=(WHITE_STRING+" ");
						break;
					case EMPTY:
						s+=(EMPTY_STRING+" ");
						break;
					}
				} catch (Exception e) {
					System.err.println("Internal error");
					e.printStackTrace();
				}
			}
			s+="\n";
			
		}
		return s;
	}
	
	public static int reverseColor(int color) throws UnknownColorException {
		if (color == BLACK) return WHITE;
		if (color == WHITE) return BLACK;
		throw new UnknownColorException(color);
	}
	
	public String labelPosition(int position) {
		if (position == PASSING_MOVE) return "Pass"; 
		int y = position % size;
		int x = (position-y)/size;
		return labelPosition(x,y);
	}

	private String labelPosition(int x, int y) {
		return ALPHABETIC_LABELS[x]+(y+1);
	}
	
	public int labelToIntPosition(String label) throws ImpossibleMoveException {
		
		if (label.equalsIgnoreCase("pass")) return PASSING_MOVE;
		
		String letter = label.substring(0, 1);
		String integer = label.substring(1);
		
		int y = Integer.valueOf(integer)-1;
		int x = -1;
		for (int j = 0 ; j < ALPHABETIC_LABELS.length ; j++) {
			if (letter.equalsIgnoreCase(ALPHABETIC_LABELS[j])) x = j;
		}
		
		int position = x*size+y;
		if (x < 0 || x >= size || y < 0 || y >= size) throw new ImpossibleMoveException(this,position);
		return position;
	}
	
	public long getHash() {
		long res = 0;
		for (int i = 0 ; i < size ; i++) {
			for (int j = 0 ; j < size ; j++) {
				res = (3*res)+board[i*size+j];
			}
		}
		return res;
	}
}
